{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# E2E Approach -- Scenario I\n",
    "\n",
    "In this notebook, we report the code related to the *E2E* appraoch in our paper [[1]](#ourpaper). We consider the delay caused by the feedback loop used by the mobile user equipment to send to the base station the channel state information (CSI). Due to this feedback delay, the CSI available at the base station becomes outdated. In our paper, we present an E2E appraoch, which takes in input the channel history available at the base station and maps it to the probability of an error event for all the modulation and coding schemes (MCSs) the base station can select. The error event corresponds to an unsuccessfully decoded frame at the receiver. If the error event probabilities are known for all the MCSs, then it is straightforward to select the MCS that maximizes the spectral efficiency.\n",
    "\n",
    "Under Scenario I, we train a different neural network for each combination of delay, doppler, and signal-to-noise-ratio.\n",
    "\n",
    "It must be noted that the training datasets listed below in the code are currently not available in the repository due to space limitations. The **training datasets can be found at**: https://kth.box.com/s/tcd7y7rg3yau75kctw3regmyns8kfkr6 in the folder *Datasets*. At any rate, in the repository, the reader can also find the codes in *radio_data* folder which can be run to generate the datasets.\n",
    "\n",
    "**Note**: the training might take some hours, depending on the available computational resources, the dimension of the training set, the dimension of the network, and the number of epochs. \n",
    "\n",
    "<a id='ourpaper'></a> [1] \"Wireless link adaptation - a hybrid data-driven and model-based approach\", Lissy Pellaco, Vidit Saxena, Mats Bengtsson, Joakim Jaldén. Submitted to SPAWC 2020."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Import libraries and utility functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import utilities as utils\n",
    "from keras.optimizers import Adam\n",
    "from keras.backend.tensorflow_backend import set_session\n",
    "from keras.backend import clear_session\n",
    "import tensorflow as tf\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Number of MCSs\n",
    "NROF_MCS=29\n",
    "# Number of subcarriers in the OFDM\n",
    "NROF_SUBCARRIERS=72\n",
    "# Flag used to indicate if the channel is noisy\n",
    "CHANNEL_EST_NOISE = True\n",
    "\n",
    "# Number of past CSI stored at the base station and used as input to the neural network\n",
    "E2E_MEMORY = 10\n",
    "BATCH_SIZE = 32\n",
    "NROF_EPOCS = 20 \n",
    "TRAINING_FRACTION = 1\n",
    "\n",
    "# Flag to indicate if the trained models should be saved\n",
    "save_model = False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load the Dataset\n",
    "\n",
    "The channel dataset is a dict with the following keys :  \n",
    " - 'channel'\n",
    "     - Complex channel coefficients \n",
    "     - Numpy array [ NROF_FRAMES x NROF_SUBCARRIERS x NROF_SNRS]\n",
    " - 'block_success'\n",
    "      - Binary success events (ACKs)\n",
    "      - Numpy array [ NROF_FRAMES x NROF_MCS x NROF_SNRS]\n",
    " - 'snrs_db '      \n",
    "     - Evaluated average SNR values\n",
    "     - Numpy array [ NROF_SNRS ]\n",
    " - 'block_sizes'\n",
    "     - Evaluated transport block sizes\n",
    "     - Numpy array [ NROF_MCS ]\n",
    "     \n",
    "The name of the dataset, e.g., ITU_VEHICULAR_B_1000_210_111_72_5dB, is to be interpreted in this way: \n",
    " - channel model (ITU_VEHICULAR_B)\n",
    " - number of channel realizations per batch (1000)\n",
    " - number of batches of the dataset (210)\n",
    " - doppler in Hz (cast to integer) (111)\n",
    " - number of subcarriers (72)\n",
    " - snr (5dB)\n",
    " \n",
    "The **training datasets can be found at**: https://kth.box.com/s/tcd7y7rg3yau75kctw3regmyns8kfkr6 in the folder *Datasets*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The files stored in the file_set ARE NOT in the repository due to space limitations.\n",
    "# The training datasets can be found at: https://kth.box.com/s/tcd7y7rg3yau75kctw3regmyns8kfkr6 in the folder *Datasets*\n",
    "# The reader has also access to the \"radio_data/Generate_Data.ipynb\" which we used to generate the training datasets.\n",
    "# N.B. for datasets with snr of 25dB numerical results show that to reach convergence a a learning rate of 0.0001 should be used\n",
    "file_set = [\n",
    "            'Datasets/ITU_VEHICULAR_B_1000_210_16.67_72_5dB.npy',\n",
    "          'Datasets/ITU_VEHICULAR_B_1000_210_16.67_72_15dB.npy',\n",
    "          'Datasets/ITU_VEHICULAR_B_1000_500_16.67_72_25dB.npy',\n",
    "        ]\n",
    "# Doppler frequency related to the datasets in \"file_set\"\n",
    "dopplers_set = (20.0 / 3.0) * np.array([16.67,16.67,16.67])\n",
    "\n",
    "# Snrs related to the datasets in \"file_set\" \n",
    "snrs_set = [5,15,25]\n",
    "\n",
    "# Number of batches related to the datasets in \"file_set\"\n",
    "num_batches_per_dataset = [210,210,500]\n",
    "\n",
    "# Maximum feedback delay that we consider\n",
    "maximum_delay = 9"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Build the Neural Network model, define the optimizer, and the cost function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def create_ann_model():\n",
    "    from keras.models import Sequential\n",
    "    from keras.layers import Dense, Dropout\n",
    "\n",
    "    model = Sequential()\n",
    "    model.add( Dense( 1024,\n",
    "                      input_dim = NROF_SUBCARRIERS * E2E_MEMORY * 2, \n",
    "                      kernel_initializer='normal', \n",
    "                      activation='relu' ) )\n",
    "\n",
    "    model.add( Dense( 512, \n",
    "                      kernel_initializer = 'normal', \n",
    "                      activation='relu' ) )\n",
    "\n",
    "    model.add( Dense( 1024,\n",
    "                      kernel_initializer = 'normal', \n",
    "                      activation='relu' ) )\n",
    "    \n",
    "    model.add( Dense( NROF_MCS, \n",
    "                      kernel_initializer='normal', \n",
    "                      activation='sigmoid' ) )\n",
    "\n",
    "    # Compile model\n",
    "    adam = Adam(lr = 0.0001, beta_1 = 0.9, beta_2 = 0.999, amsgrad = False)\n",
    "    model.compile(loss = 'binary_crossentropy', optimizer = adam, metrics = ['accuracy'])  # for binary classification\n",
    "\n",
    "    \n",
    "    return model\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Train the Neural Network"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "start_time = time.time()\n",
    "\n",
    "for DELAY in range(0,maximum_delay + 1):\n",
    "    \n",
    "    for i in range(0,len(snrs_set)):  \n",
    "        \n",
    "        DOPPLER=dopplers_set[i]\n",
    "        FILE=file_set[i]\n",
    "        SELECTED_SNR=snrs_set[i]\n",
    "        DATASET = np.load( FILEfile, allow_pickle = True )[()]\n",
    "        \n",
    "        config = tf.ConfigProto()\n",
    "        config.gpu_options.allow_growth = True  \n",
    "\n",
    "        sess = tf.Session( config = config )\n",
    "        set_session(sess) \n",
    "        \n",
    "\n",
    "        model = create_ann_model( )\n",
    "\n",
    "        for j in range(0,num_batches_per_dataset[i]):    \n",
    "\n",
    "            channel_coeff  = []\n",
    "            block_success  = []\n",
    "        \n",
    "            DATASET_channel=(DATASET['channel'][:,:,j])  \n",
    "            DATASET_block_success=(DATASET['block_success'][:,:,j]) \n",
    "\n",
    "            nrof_train_samples = int( TRAINING_FRACTION * DATASET_channel.shape[0] )\n",
    "            coeff = utils.calculate_channel_coefficients_scaled_fixed_snr( DATASET_channel[ :nrof_train_samples, : ],\n",
    "                                                                 SELECTED_SNR,\n",
    "                                                                 channel_estimation_noise = CHANNEL_EST_NOISE )\n",
    "\n",
    "            channel_coeff.append( coeff )\n",
    "            block_success.append( DATASET_block_success[ :nrof_train_samples, : ] )\n",
    "\n",
    "            channel_coeff = np.vstack( channel_coeff )\n",
    "            block_success = np.vstack( block_success )\n",
    "\n",
    "\n",
    "            BLOCK_SIZES = DATASET[ 'block_sizes' ] \n",
    "\n",
    "\n",
    "            NROF_FRAMES,_= channel_coeff.shape\n",
    "\n",
    "\n",
    "            channel_coeff_concat = np.concatenate( ( np.real( channel_coeff ), np.imag( channel_coeff ) ), axis = 1 )\n",
    "\n",
    "\n",
    "            if DELAY > 0:\n",
    "                train_input  = ( channel_coeff_concat[ :-DELAY, :] )\n",
    "            else:\n",
    "                train_input  = ( channel_coeff_concat[:,:] )\n",
    "\n",
    "            train_target = ( block_success [ DELAY :, :] )\n",
    "\n",
    "            train_input, train_target = utils.shuffle_data( train_input, train_target )\n",
    "\n",
    "\n",
    "\n",
    "            history = model.fit( utils.stack_features( train_input, ANN_MEMORY ), \n",
    "                                         train_target, \n",
    "                                         batch_size = BATCH_SIZE, \n",
    "                                         epochs     = NROF_EPOCS, \n",
    "                                         validation_split = 0.1, \n",
    "                                         verbose    = 0 ) # the \"verbose parameter\" can be changed to display more about the training progess of each epoch\n",
    "\n",
    "\n",
    "        \n",
    "        file_to_save = 'Trained_models_ScenarioI/ANN_E2E_MCS_PRED_DELAY_%d_DP_%d_SNR_%d.h5'%(DELAY,DOPPLER,SELECTED_SNR)\n",
    "        if save_model == True:      \n",
    "            model.save( file_to_save )\n",
    "\n",
    "        print(\"--- %s seconds ---\" % (time.time() - start_time))\n",
    "        clear_session( )\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
